package com.gitee.huanminabc.utils_test.mybatisplus;

import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.query.Query;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.reflection.property.PropertyNamer;

import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

/**
 * @Description:
 * 为啥要写这个类呢? 因为mybatis-plus的QueryWrapper不支持lambda表达式的查询,只支持字符串形式的查询,而LambdaQueryWrapper不支持函数相关的查询， 想要鱼和熊掌兼得,所以写了这个类
 * 场景： 一个页面中  分页查询+统计查询 ，查询条件是一样的情况如果使用lambda方式，列上就不能使用聚合函数，这个类就是为了解决这个问题 ，同时还解决了分组后使用聚合函数的问题
 * 1. 支持lambda表达式和普通的查询
 * 2. 支持lambda表达式sum,avg,max,min聚合函数查询
 * 3. eq 加密字段无须手动加密,会根据字段上的@TableField(typeHandler = DataEncryptHandler.class)注解自动加密， 支支持lambda表达式方式, 如果是手写字符串的方式,需要手动加密
 * @Author huanmin
 * @Date 2024/5/22 下午6:02
 */
public class CustomQueryWrapper<T> extends AbstractWrapper<T, String, CustomQueryWrapper<T>>
        implements Query<CustomQueryWrapper<T>, T, String> {

    private Map<String, ColumnCache> columnMap = null;
    private boolean initColumnMap = false;
    /**
     * 查询字段
     */
    private final SharedString sqlSelect = new SharedString();

    public CustomQueryWrapper() {
        this(null);
    }

    public CustomQueryWrapper(T entity) {
        super.setEntity(entity);
        super.initNeed();
    }

    public CustomQueryWrapper(T entity, String... columns) {
        super.setEntity(entity);
        super.initNeed();
        this.select(columns);
    }

    /**
     * 非对外公开的构造方法,只用于生产嵌套 sql
     *
     * @param entityClass 本不应该需要的
     */
    private CustomQueryWrapper(T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
                               Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
                               SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
        super.setEntity(entity);
        super.setEntityClass(entityClass);
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.paramAlias = paramAlias;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
        this.sqlFirst = sqlFirst;
    }

    @Override
    public CustomQueryWrapper<T> select(String... columns) {
        if (ArrayUtils.isNotEmpty(columns)) {
            this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
        }
        return typedThis;
    }

    @Override
    public CustomQueryWrapper<T> select(boolean condition, List<String> columns) {
        if (condition && CollectionUtils.isNotEmpty(columns)) {
            this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
        }
        return typedThis;
    }


    @Override
    public CustomQueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
        super.setEntityClass(entityClass);
        this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(getEntityClass()).chooseSelect(predicate));
        return typedThis;
    }

    @Override
    public String getSqlSelect() {
        return sqlSelect.getStringValue();
    }



    /**
     * 用于生成嵌套 sql
     * <p>
     * 故 sqlSelect 不向下传递
     * </p>
     */
    @Override
    protected CustomQueryWrapper<T> instance() {
        return new CustomQueryWrapper<>(getEntity(), getEntityClass(), paramNameSeq, paramNameValuePairs, new MergeSegments(),
                paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString());
    }

    @Override
    public void clear() {
        super.clear();
        sqlSelect.toNull();
    }


    public CustomQueryWrapper<T> sum(SFunction<T, ?> column){
        String columnToString = columnToString(column);
        this.sqlSelect.setStringValue("SUM(" + columnToString + ") "+columnToString);
        return typedThis;
    }
    public CustomQueryWrapper<T> sum(SFunction<T, ?> column, SFunction<T, ?> alias) {
        String columnToString = columnToString(column);
        String aliasToString = columnToString(alias);
        this.sqlSelect.setStringValue("SUM(" + columnToString + ")  " + aliasToString);
        return typedThis;
    }

    @SafeVarargs
    public final CustomQueryWrapper<T> sum(SFunction<T, ?>... column){
        if (ArrayUtils.isNotEmpty(column)) {
            StringJoiner joiner = new StringJoiner(StringPool.COMMA);
            for (SFunction<T, ?> sFunction : column) {
                String columnToString = columnToString(sFunction);
                joiner.add("SUM(" + columnToString + ") "+columnToString);
            }
            this.sqlSelect.setStringValue(joiner.toString());
        }
        return typedThis;
    }


    //avg
    public CustomQueryWrapper<T> avg(SFunction<T, ?> column, SFunction<T, ?> alias) {
        String columnToString = columnToString(column);
        String aliasToString = columnToString(alias);
        this.sqlSelect.setStringValue("AVG(" + columnToString + ")  " + aliasToString);
        return typedThis;
    }

    //max
    public CustomQueryWrapper<T> max(SFunction<T, ?> column, SFunction<T, ?> alias) {
        String columnToString = columnToString(column);
        String aliasToString = columnToString(alias);
        this.sqlSelect.setStringValue("MAX(" + columnToString + ")  " + aliasToString);
        return typedThis;
    }

    //min
    public CustomQueryWrapper<T> min(SFunction<T, ?> column, SFunction<T, ?> alias) {
        String columnToString = columnToString(column);
        String aliasToString = columnToString(alias);
        this.sqlSelect.setStringValue("MIN(" + columnToString + ")  " + aliasToString);
        return typedThis;
    }

    public CustomQueryWrapper<T> select(SFunction<T, ?>... columns) {
        if (ArrayUtils.isNotEmpty(columns)) {
            StringJoiner joiner = new StringJoiner(StringPool.COMMA);
            for (SFunction<T, ?> column : columns) {
                String columnToString = columnToString(column);
                joiner.add(columnToString);
            }
            this.sqlSelect.setStringValue(joiner.toString());
        }
        return typedThis;
    }


    public CustomQueryWrapper<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
        String columnToString = columnToString(column);
        val = valuePreprocess(columnToString, val);
        eq(condition, columnToString, val);
        return this;
    }


    public CustomQueryWrapper<T> eq(SFunction<T, ?> column, Object val) {
        String columnToString = columnToString(column);
        val = valuePreprocess(columnToString, val);
        return eq(columnToString, val);
    }

    public CustomQueryWrapper<T> like(boolean condition, SFunction<T, ?> column, Object val) {
        String columnToString = columnToString(column);
        return super.like(condition, columnToString, val);
    }

    public CustomQueryWrapper<T> likeRight(boolean condition, SFunction<T, ?> column, Object val) {
        String columnToString = columnToString(column);
        return super.likeRight(condition, columnToString, val);
    }

    public CustomQueryWrapper<T> likeLeft(boolean condition, SFunction<T, ?> column, Object val) {
        String columnToString = columnToString(column);
        return super.likeLeft(condition, columnToString, val);
    }

    public CustomQueryWrapper<T> between(boolean condition, SFunction<T, ?> column, Object val1, Object val2) {
        String columnToString = columnToString(column);
        between(condition, columnToString, val1, val2);
        return this;
    }

    public CustomQueryWrapper<T> orderByDesc(SFunction<T, ?> column) {
        String columnToString = columnToString(column);
        orderByDesc(columnToString);
        return this;
    }

    //后期这个可以做成策略模式 ,只支持lambda的方式进行处理
    public Object valuePreprocess(String column, Object value) {
//        if (value == null) {
//            return value;
//        }
//        if (!(value instanceof String)) {
//            return value;
//        }
//        if (column==null){
//            return value;
//        }
//        //将字符串转换为大写
//        String formatKey = LambdaUtils.formatKey(column);
//        //columnMap 为空的时候初始化
//        tryInitCache(getEntityClass());
//        //查询缓存
//        ColumnCache columnCache = columnMap.get(formatKey);
//        if (columnCache == null) {
//            //如果是空的那么就去掉下划线在试试
//            formatKey=formatKey.replaceAll("_", "");
//            columnCache = columnMap.get(formatKey);
//        }
//        if (columnCache!=null&&columnCache.getMapping() != null && columnCache.getMapping().contains("typeHandler") && columnCache.getMapping().contains("DataEncryptHandler")) {
//            value = AesEncryptUtil.encrypt(value.toString());
//        }
        return value;
    }


    protected String columnToString(SFunction<T, ?> column) {
        ColumnCache cache = getColumnCache(column);
        return cache.getColumn();
    }

    protected ColumnCache getColumnCache(SFunction<T, ?> column) {
        LambdaMeta meta = LambdaUtils.extract(column);
        String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
        Class<?> instantiatedClass = meta.getInstantiatedClass();
        tryInitCache(instantiatedClass);
        //缓存中的字段一般都会被转换为大写, 如果数据库的字段是a_b 那么缓存中的字段就是AB ,会兼容这种
        ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
        if (columnCache == null) {
            //如果缓存中查询不到,那么就返回一个默认的,因为这个可能就不是数据库字段而是一个自定义的字段
            //一般都是 @TableField(exist = false) 的字段
            //原始的字段名返回
            return new ColumnCache(fieldName, fieldName);
        }
        return columnCache;

    }

    private void tryInitCache(Class<?> lambdaClass) {
        if (!initColumnMap) {
            final Class<T> entityClass = getEntityClass();
            if (entityClass != null) {
                lambdaClass = entityClass;
            }
            columnMap = LambdaUtils.getColumnMap(lambdaClass);
            Assert.notNull(columnMap, "can not find lambda cache for this entity [%s]", lambdaClass.getName());
            initColumnMap = true;
        }
    }


}

